//
//  ZBorder.swift
//  AG
//
//  Created by zhuxuhong on 2020/11/15.
//  Copyright © 2020 AgoCulture. All rights reserved.
//

import UIKit
import BWToolKit


extension UIView {
    public enum ZBorderPosition: String {
        case top = "0"
        case left = "1"
        case bottom = "2"
        case right = "3"
        case all = "4"
    }
    
    public func setupZBorders(
        postions: Set<ZBorderPosition> = [.all],
        colors: [ZBorderPosition: UIColor] = [.all: .black],
        widths: [ZBorderPosition: CGFloat] = [.all: 1],
        spaces: [ZBorderPosition: CGFloat] = [.all: 0]
    ){
        zBordersEnabled = true
        zBorderPositions = postions
            .sorted(by: \.rawValue)
            .map{ $0.rawValue }
            .joined(separator: ",")
        
        zBorderColors = (colors.count == 1 && colors.first?.key == .all)
        ? colors.first!.value.hexString
        :
        colors
            .sorted(by: \.key.rawValue)
            .map{ $0.value.hexString }
            .joined(separator: ",")
        
        zBorderWidths = (widths.count == 1 && widths.first?.key == .all)
        ? "\(widths.first!.value)"
        :
        widths
            .sorted(by: \.key.rawValue)
            .compactMap{ "\($0.value)" }
            .joined(separator: ",")
        
        zBorderInsets = (spaces.count == 1 && spaces.first?.key == .all)
        ? "\(spaces.first!.value)"
        :
        spaces
            .sorted(by: \.key.rawValue)
            .compactMap{ "\($0.value)" }
            .joined(separator: ",")
        
        layer.setNeedsDisplay()
    }
    
    fileprivate func _makeZBorders(in rect: CGRect, ctx: CGContext){
        guard zBordersEnabled else {
            return
        }
        ctx.saveGState()
        ctx.clear(rect)
        
        /// Insets
        let insetsValues = zBorderInsets
            .components(separatedBy: ",")
            .prefix(4)
            .compactMap{ Float($0) }
        var insets = UIEdgeInsets.zero
        if insetsValues.count == 1 {
            let v = CGFloat(insetsValues.first!)
            insets = .init(top: v, left: v, bottom: v, right: v)
        }
        else if insetsValues.count == 4 {
            insets.top = CGFloat(insetsValues[0])
            insets.left = CGFloat(insetsValues[1])
            insets.bottom = CGFloat(insetsValues[2])
            insets.right = CGFloat(insetsValues[3])
        }
        
        /// Colors
        var colors = zBorderColors
            .components(separatedBy: ",")
            .prefix(4)
            .compactMap{ UIColor(hexString: $0) }
        var defaultColor = UIColor.black
        if colors.count == 1 {
            defaultColor = colors.first!
        }
        if colors.count < 4 {
            let r = 4 - colors.count
            for _ in 0..<r {
                colors.append(defaultColor)
            }
        }
        
        /// Postitions
        var positions = zBorderPositions
            .components(separatedBy: ",")
            .bw_toSet()
            .compactMap { ZBorderPosition(rawValue: $0) }
        if positions.isEmpty {
            positions = [.top, .left, .bottom, .right]
        }
        else if positions.count == 1 && positions.first == .all {
            positions = [
                .top, .left, .bottom, .right
            ]
        }
        
        /// Widths
        var widths = zBorderWidths
            .components(separatedBy: ",")
            .prefix(4)
            .compactMap{ Float($0) }
        var defaultWidth: Float = 1
        if widths.count == 1 {
            defaultWidth = widths.first!
        }
        if widths.count < 4 {
            let r = 4 - widths.count
            for _ in 0..<r {
                widths.append(defaultWidth)
            }
        }
        
        /// Quick values
        let tWidth = CGFloat(widths[0])
        let lWidth = CGFloat(widths[1])
        let bWidth = CGFloat(widths[2])
        let rWidth = CGFloat(widths[3])
        
        let tColor = colors[0].cgColor
        let lColor = colors[1].cgColor
        let bColor = colors[2].cgColor
        let rColor = colors[3].cgColor
        
        for p in positions
        {
            switch p {
            case .top:
                let path = UIBezierPath(from: .init(
                    x: insets.left,
                    y: insets.top + tWidth/2
                ), to: .init(
                    x: rect.maxX - insets.right,
                    y: insets.top + tWidth/2
                ))
                ctx.setLineWidth(tWidth)
                ctx.setStrokeColor(tColor)
                ctx.addPath(path.cgPath)
                ctx.strokePath()
                
            case .left:
                let path = UIBezierPath(from: .init(
                    x: insets.left + lWidth/2,
                    y: insets.top
                ), to: .init(
                    x: insets.left + lWidth/2,
                    y: rect.maxY - insets.bottom
                ))
                ctx.setLineWidth(lWidth)
                ctx.setStrokeColor(lColor)
                ctx.addPath(path.cgPath)
                ctx.strokePath()
                
            case .right:
                let path = UIBezierPath(from: .init(
                    x: rect.maxX - insets.right - rWidth/2,
                    y: insets.top
                ), to: .init(
                    x: rect.maxX - insets.right - rWidth/2,
                    y: rect.maxY - insets.bottom
                ))
                ctx.setLineWidth(rWidth)
                ctx.setStrokeColor(rColor)
                ctx.addPath(path.cgPath)
                ctx.strokePath()
                
            case .bottom:
                let path = UIBezierPath(from: .init(
                    x: insets.left,
                    y: rect.maxY - insets.bottom - bWidth/2
                ), to: .init(
                    x: rect.maxX - insets.right,
                    y: rect.maxY - insets.bottom - bWidth/2
                ))
                ctx.setLineWidth(bWidth)
                ctx.setStrokeColor(bColor)
                ctx.addPath(path.cgPath)
                ctx.strokePath()
            default: break
            }
        }
        // for-loop ended here
        
        ctx.restoreGState()
    }
}

extension UIView {
    private struct AKeys{
        static var zBordersEnabled = "zBordersEnabled"
        static var zBorderPositions = "zBorderPositions"
        static var zBorderColors = "zBorderColors"
        static var zBorderWidths = "zBorderWidths"
        static var zBorderInsets = "zBorderInsets"
    }
    
    @IBInspectable var zBordersEnabled: Bool {
        get{
            return Objcer.getASValue(for: self, key: &AKeys.zBordersEnabled) as? Bool ?? false
        }
        set{
            Objcer.setASValue(newValue, for: self, key: &AKeys.zBordersEnabled, policy: .assign)
        }
    }
    
    @IBInspectable var zBorderPositions: String {
        get{
            return Objcer.getASValue(for: self, key: &AKeys.zBorderPositions) as? String ?? ""
        }
        set{
            Objcer.setASValue(newValue, for: self, key: &AKeys.zBorderPositions, policy: .copy)
        }
    }
    
    @IBInspectable var zBorderColors: String {
        get{
            return Objcer.getASValue(for: self, key: &AKeys.zBorderColors) as? String ?? ""
        }
        set{
            Objcer.setASValue(newValue, for: self, key: &AKeys.zBorderColors, policy: .copy)
        }
    }
    
    @IBInspectable var zBorderWidths: String {
        get{
            return Objcer.getASValue(for: self, key: &AKeys.zBorderWidths) as? String ?? ""
        }
        set{
            Objcer.setASValue(newValue, for: self, key: &AKeys.zBorderWidths, policy: .copy)
        }
    }
    
    @IBInspectable var zBorderInsets: String {
        get{
            return Objcer.getASValue(for: self, key: &AKeys.zBorderInsets) as? String ?? ""
        }
        set{
            Objcer.setASValue(newValue, for: self, key: &AKeys.zBorderInsets, policy: .copy)
        }
    }
    
    @objc public static func setupZBordersLoad(){
        let cls = Self.self
        Objcer.swizzleSelector1(#selector(draw(_:in:)), inClass1: cls, withSelector2: #selector(sBordersDraw(_:in:)), inClass2: cls)
        Objcer.swizzleSelector1(#selector(awakeFromNib), inClass1: cls, withSelector2: #selector(sBordersAwakeFromNib), inClass2: cls)
        Objcer.swizzleSelector1(#selector(layoutSubviews), inClass1: cls, withSelector2: #selector(sBordersLayoutSubviews), inClass2: cls)
    }
    
    @objc private func sBordersAwakeFromNib(){
        sBordersAwakeFromNib()
        
        layer.setNeedsDisplay()
    }
    
    @objc private func sBordersLayoutSubviews(){
        sBordersLayoutSubviews()
        
        layer.setNeedsDisplay()
    }
    
    @objc private func sBordersDraw(_ layer: CALayer, in ctx: CGContext) {
        sBordersDraw(layer, in: ctx)
        
        _makeZBorders(in: layer.bounds, ctx: ctx)
    }
    
}

/*
extension UIView {
    static let ZBorderTag = 23198201
    
    public var ZBorders: [UIView] {
        let within: Range = (Self.ZBorderTag..<Self.ZBorderTag+4)
        return subviews.filter{ within.contains($0.tag) }
    }
    
    private func _findZBorder(_ pos: ZBorder.Position) -> UIView? {
        return ZBorders.first{ ($0.tag - Self.ZBorderTag) == pos.rawValue }
    }
    
    public var leftZBorder: UIView? {
        return _findZBorder(.left)
    }
    
    public var rightZBorder: UIView? {
        return _findZBorder(.right)
    }
    
    public var bottomZBorder: UIView? {
        return _findZBorder(.bottom)
    }
    
    public var topZBorder: UIView? {
        return _findZBorder(.top)
    }
    
    @discardableResult public func addZBorders(
        insets: UIEdgeInsets = .zero,
        color: UIColor = .white.alpha(0.1)
    ) -> [UIView]
    {
        return addLines([
            .init(position: .left, color: color, insets: .with(top: insets.top, left: insets.left, bottom: insets.bottom)),
            .init(position: .top, color: color, insets: .with(top: insets.top, left: insets.left, right: insets.right)),
            .init(position: .right, color: color, insets: .with(top: insets.top, bottom: insets.bottom, right: insets.right)),
            .init(position: .bottom, color: color, insets: .with(left: insets.left, bottom: insets.bottom, right: insets.right))
        ])
    }
    
    @discardableResult public func addLines(_ ZBorders: [ZBorder]) -> [UIView] {
        var views: [UIView] = []
        for item in ZBorders {
            views.append(addLine(item))
        }
        return views
    }
    
    @discardableResult public func addZBorder(_ position: ZBorder.Position = .bottom) -> UIView {
        let v = addLine(.init(position: position))
        v.tag = Self.ZBorderTag + position.rawValue
        return v
    }
    
    @discardableResult public func addLine(_ ZBorder: ZBorder = .init()) -> UIView
    {
        let v = UIView()
        v.backgroundColor = ZBorder.color
        addSubview(v)
        
        let insets = ZBorder.insets!
        let h = ZBorder.height!
        let pos = ZBorder.position!
        switch pos
        {
        case .top:
            v.snp.makeConstraints {
                $0.leading.equalTo(insets.left)
                $0.trailing.equalTo(-insets.right)
                $0.top.equalTo(insets.top)
                $0.height.equalTo(h)
            }
        case .bottom:
            v.snp.makeConstraints {
                $0.leading.equalTo(insets.left)
                $0.trailing.equalTo(-insets.right)
                $0.bottom.equalTo(-insets.bottom)
                $0.height.equalTo(h)
            }
        case .left:
            v.snp.makeConstraints {
                $0.top.equalTo(insets.top)
                $0.leading.equalTo(insets.left)
                $0.bottom.equalTo(-insets.bottom)
                $0.width.equalTo(h)
            }
        case .right:
            v.snp.makeConstraints {
                $0.top.equalTo(insets.top)
                $0.trailing.equalTo(-insets.right)
                $0.bottom.equalTo(-insets.bottom)
                $0.width.equalTo(h)
            }
        default: break
        }
        
        return v
    }
}
*/
